Skip to content

MobX 7#4671

Open
kubk wants to merge 82 commits into
mobxjs:mainfrom
kubk:mobx-v7
Open

MobX 7#4671
kubk wants to merge 82 commits into
mobxjs:mainfrom
kubk:mobx-v7

Conversation

@kubk

@kubk kubk commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

#3796

  • remove legacy decorators
  • remove non-proxy support
  • clean up old code from mobx-react and mobx-react-lite (class components are still supported)
    • remove deprecated hooks
    • remove prop types
  • removed unstable_batchedUpdates (both react web & react native do it automatically). new tests included proving it's no longer needed
  • remove provider / inject. new tests included for the react + context usage
    • document how to migrate to react context instead. for both function components and class components
  • remove trace
  • drop react <=17
  • v7 migration guide
  • update all documentation
  • upgrade build tooling to use plain rollup. keep CJS and modern ESM
  • scripts to check code splitting
  • split comparer.* into compare* as well as flow.* / action.* into flow* / action* etc for better tree-shaking: Add computed.shallow annotation #2986
  • observe/intercept partial tree-shaking
Package Before After Reduction %
mobx bundle ESM 17.02 KiB 13.96 KiB 18.0%
mobx minimal* ESM 14.66 KiB 10.32 KiB 29.6%
mobx-react-lite 1.81 KiB 1.53 KiB 15.5%
mobx-react 3.71 KiB 1.79 KiB 51.8%

*minimal only exports observable, action, computed, and autorun. All the measurement scripts are in the PR.

I also tried a more aggressive change - removing decorators from the main bundle entirely and moving them into a separate mobx/decorator module. To my surprise, that only saved about 0.8 KB gzipped

Yet this required a pretty serious overhaul of the library internals, and I'm not sure it is stable enough to release. Considering the very modest gains, I called this out. The PR doesn't include it.

In general the V7 should be pretty safe to migrate to, as it's mostly cleanup

@changeset-bot

changeset-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 30eef8f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
mobx Major
mobx-react-lite Major
mobx-react Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/mobx/src/utils/comparer.ts Outdated
Comment thread packages/mobx/src/types/observablearray.ts Outdated
Comment thread packages/mobx/src/types/observablearray.ts Outdated

@js2me js2me left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing PR!
LGTM

@merlinaudio

merlinaudio commented Jun 30, 2026

Copy link
Copy Markdown

amazing ✨🚀

thank you for working on this :)

@mweststrate

Copy link
Copy Markdown
Member

@kubk

ObservableValue.enhancer

If MST doesn't use it anymore (that is what is was introduced for), I think we could drop the feature just altogether. At it's core it is a feature that should imho be solved in userland, not inside the library, and it will remove a bunch of indirection / inefficiency in how values are processed.

@mweststrate

Copy link
Copy Markdown
Member

The other changes look great!

@mweststrate

Copy link
Copy Markdown
Member

Beyond that LGTM! Happy to help with further merge and release / version number coordination. Not surely entirely how I did that with previous majors, but muscle memory will hopefully return when going through the motions :). So let me know when you're happy @kubk and I can merge & cut.

@kubk

kubk commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

@mweststrate Thanks!

It does seem that ObservableValue.enhancer can be removed, but I won’t be able to find the time to properly clean it up in the next couple of weeks and I don't want that to become a blocker for the v7 release. So if the rest looks ok, please proceed with the merge and the beta release!

Also I was hoping we could get the minimal MobX example to ~10 kB gzip (a beautiful number!) I tried many different approaches to reduce the output size. Some helped, some didnt and at this point I'm out of ideas

If you see any obvious size wins, i'd really appreciate your help there too. There's npm run check-size script in the repo root now

@mweststrate

Copy link
Copy Markdown
Member

I have some obligations this week I can't get around, but happy to circle back to this next week!

@kubk

kubk commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

The tree-shaken Mobx is now down to 10.3 kb gzip 🥳 Hope this round of optimizations is the last one! It might seem like a lot but it's actually all about moving / removing things.

Optimizations:

  • allowStateChangesStart is out of the prod bundle
  • bit flags are now erasable enums
  • intercept / observe are partially excluded from the main bundle*
  • adm.make_ has been moved to makeObservable so its not bundled in prod if we use plain observable({ }) api (without makeObservable / makeAutoObservable)
  • rollup-plugin-terser has been replaced with @rollup/plugin-terser as the old one is out of date

*By "intercept / observe partially excluded" I mean:

  • observe/intercept and registerListener / registerInterceptor are out of the main bundle. But hasListeners, notifyListeners, hasInterceptors, interceptChange are still there since they're baked in observable classes.

I also analyzed how the enhancer works to see what can be optimized. As I understand - the enhancer choice must persist with observable value through the whole it's lifecycle. So instead of having ObservableValue.enhancer_ we could store TS enum in Atom like enum Enhancer { Deep, Ref, Shallow, Struct } but that leads us to having a dispatcher like

function enhanceValue(
    enhancer: Enhancer,
    newValue,
    oldValue,
    name
) {
    switch (enhancer) {
        case Enhancer.Deep: return deepEnhancer(...);
        case Enhancer.Ref: return newValue;
        case Enhancer.Shallow: return shallowEnhancer(...);
        case Enhancer.Struct: return refStructEnhancer(newValue, oldValue, name)
    }
}

And that will increases the bundle size from 10.32 kb to 10.86 kb gzip (because all of the enhancers are included). Please let me know if anything is missing here! As for "improving efficiency" it seems like just merging #4683 will already provide a lot of value

To summarize i'd really appreciate your help with 2 things:

Thank you!

@mweststrate

Copy link
Copy Markdown
Member

@kubk I just realized I misnomed, meant dehancer instead of enhancer. My current thinking is (I have some WIP changes for this, can keep pushing on this). All disputable

  1. Remove dehancer, ObservableValue.raw (this locks MST to the previous major I think, I'm not entirely sure if it can work without these two backdoors? unless it is fully proxy based now)
  2. Remove intercept / observe. These two functions exist to make MobX some sort of swiss army knife of an higher order abstract library to basically provide default proxy trap patterns to the core JS collections Object/Map/Array/Set. I think this however goes in principle outside the scope of MobX and they don't really belong inside MobX, as they don't do anything with reactivity. If people wanted to do this kind of object trapping, nowadays they can just directly proxy wrap themselves.
  3. Remove spy, and getDebugName and the observer tree utilities (even though they mostly fall outside of bundling anyway). It is all quite a lot of code, and I'm not really sure people really use it for debugging, since it requires code changes. Well placed console.logs / breakpionts and following idiomatic patterns should be sufficient to debug MobX? And in a world of increasing agentic development, I think there is less and less need to expose those utilities to make it possible to build rich debug tools.
  4. Remove all the object-api's, keys, values, entries, these are remnants of the pre Proxy world, where these offered a work around to reactively read / observe otherwise unobservable object mutations.
  5. toJS might arguable belong better in mobx-utils, but I'm a little reluctant to remove it for I don't know which reason :)
  6. Several of the _ prefixed APIs exposed in mobx. I'm not sure any should be exposed, outside the ones used for tests (and there might be a better way to set that up).

What do you, and others, think?

@mattruby

mattruby commented Jul 13, 2026 via email

Copy link
Copy Markdown
Member

@kubk

kubk commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator Author

@mweststrate I'm totally up for this direction. A few notes on each point

Remove intercept / observe. These two functions exist to make MobX some sort of swiss army knife of an higher order abstract library to basically provide default proxy trap patterns to the core JS collections Object/Map/Array/Set. I think this however goes in principle outside the scope of MobX and they don't really belong inside MobX, as they don't do anything with reactivity. If people wanted to do this kind of object trapping, nowadays they can just directly proxy wrap themselves.

1-2 - I haven't used MST or mobx-keystone myself but it seems like they both use intercept / observe. Also in this branch both of the functions are already partially tree-shaken. @coolsoftwaretyler @xaviergonz - sorry for pulling you into a large PR! Would appreciate your thoughts on the suggested intercept / observe removal.

3 - I wouldn't mind removing the current spy / getDebugName. AI assisted debugging makes temporary instrumentation much cheaper now. An agent can add the relevant console.logs and inspect the result through Chrome MCP. Yet, spy does not affect the prod bundle, it did not seem to complicate my work with Mobx and it powers mobx-log (which i admittedly stopped using since the rise of AI)

4 - Makes total sense

5 - toJS is already completely tree-shaken, so maybe it's not enough by itself to justify moving such a commonly used API

6 - I am also up for cleaning this up, where possible. MST is still using a couple of such functions like _getAdministration / _interceptReads / _getGlobalState. The latter is also used by mobx-react-lite to verify that its running alongside Mobx 7

@coolsoftwaretyler

coolsoftwaretyler commented Jul 13, 2026

Copy link
Copy Markdown

@kubk - from what I can see, we use intercept and observe at some of the most fundamental layers of MobX-State-Tree. Removing those functions from MobX would require us to move those functions into the library itself.

My goal with the library has been to prioritize stability, so I'd personally vote against the removal from MobX, purely from the stance that I try to avoid making changes to MST these days. But with AI, it's not a huge burden for me to reimplement these functions in MST. We have pretty thorough test suites that should make the transition smooth.

@jamonholmgren

Copy link
Copy Markdown
Contributor

It would be cool to have a mobx-compat library that adds back in the removed functionality. Swap to mobx-compat everywhere and continue trucking along.

@xaviergonz

Copy link
Copy Markdown
Contributor

Thanks for the heads up!

mobx-keystone relies on both APIs for core tree semantics:

  • intercept is its synchronous pre-write hook. It enforces write protection, validates/tweaks incoming values, and performs detach/reparent work before MobX commits a change. A post-change mechanism would be too late, since the value must be transformed or the mutation rejected before it becomes visible.
  • observe is its post-write hook. It updates snapshots and emits patches/deep-change notifications from the concrete object/array/map change, including its old and new values. It is also used to keep observable array/map/set wrappers in sync.

I checked MST as well: its model, array, and map types use the same intercept / observe funcs for writable checks, child reconciliation, and patch emission.

The current tree-shaking improvement looks great, but removing these APIs outright would be a breaking change for both libraries unless there is a supported replacement with equivalent synchronous pre-commit interception and structured post-commit change events. reaction alone would not be equivalent, since it runs after the mutation and does not provide the same change payload.

If there is an alternative API or a separate opt-in module in mind, I’d be happy to test mobx-keystone against it and help validate a migration path.

@mweststrate

Copy link
Copy Markdown
Member

@coolsoftwaretyler , @jamonholmgren , @xaviergonz Hi! thanks for piling in.

Some thoughts:

  1. I think it is tricky to implement these APIs outside of MobX, e.g inside a mobx-compat layer or MST / keystone directly. They are quite in the middle of core updating methods, so even monkey patching the classes likely wouldn't work (especially for intercept), since both before or after the base functions would likely be the wrong time to do this. Although in practice I think intercepting purely before could work as well, but that was something in just noted in the current implementation.
  2. I think the proper way to fix this in MST/keystone is to not hitch on top of MobX objects at all, but just have their own first class primitives (array, object, map etc, that can do the necessary pre/post processing and then delegate to mobx objects (or even atoms).
  3. I've been long playing with the idea of having a mobx-lite version that has all the cleanups I envision without having all users paying the tax for features used by a couple deeply integrated, but relatively less frequently used MST/keystone libraries.
  4. So basically I think Mobx 7 is my vision for mobx-lite (honestly I'd love to remove mobx-react-lite -> mobx-react distinction as well, there is just little ROI for that right now).
  5. However, I totally love the concepts of MST and keystone. And honestly I'd love to build a new version of MST, based fully on proxies and modern TS types that could remove practically all quirks of MST (I think). I just never had the time for it over the past couple of years, but who knows that changes due to changes in personal circumstances.
  6. I think my preferred solution to this would be to keep maintaining MobX 6 as well. Mobx7 doesn't introduce any new features, and fixes are a very slow trickly, that I think at this point it is probalby fine to pay the upkeep for both (if you don't mind not maintaining the docs for two versions 😅). How does it sound to keep just MobX 6 active and compatible with MST/keystone?
  7. I'm happy for us to instead explore a mobx-compat library as well, I think as suggested that could be made to work. But I also suspect it would just be more work (on the upside, well defined tests, so claude/codex could probably do most of the work and I might just be overthinking this)

@merlinaudio

Copy link
Copy Markdown

Not that it's my place to speak, but as an end user, I had always wished for mobx' API surface to become a bit leaner and more constrained in what mobx offers vs. what external packages should handle

And it is a new major, after all. I would not be terribly unhappy with a breaking change of this sort

So, as an end user, I'd love to see intercept/observe moved out

@kubk

kubk commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

@mweststrate I am also up for the leanest possible Mobx - no intercept/observe, no debug utilities, no decorators (in the core package), minimum options inside configure. 100% of my projects would work absolutely fine under this setup. The only less common API i use a lot is onBO / onBU.

But MST/mobx-keystone have ~200k weekly downloads combined so locking them to the outdated Mobx 6 seems like a miss

What if we

  • Release current progress as Mobx 7. It's compatible with MST/mobx-keystone with only minor tweaks. Everyone benefits from the size and legacy reduction
  • Make Mobx 8 the leanest possible version, maybe even a complete rewrite. We do it later when we have time without opening pandora box with a full overhaul right now. We can maintain both v7 for libraries and v8 as a default recommendation for new projects

The reason i suggest 7 and 8 instead of 6 and 7 is because v6 carries a ton of legacy. And if we're going to support 2 mobx versions anyway, we might as well make the older one the cleaned up v7 instead of carrying all of v6 indefinitely

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants